﻿<?php if (!defined('INDEX')) { trigger_error("Index is not defined!", E_USER_ERROR); exit(); }
/**
 * Класс предоставляет api для получения закладок указанного в параметрах пользователя с сайта habrahabr.ru
 * @package      habrabookmarks
 *
 * @author       Max Kamashev <max.kamashev@floscoeli.com>
 * @copyright    floscoeli.com ©2009
 * @license      http://opensource.org/licenses/gpl-3.0.html
 * @version      SVN: $Id: HabraHabr.service.php 31 2009-03-18 07:50:35Z M.Kamashev $
 *
 * settings:     ts: 4 spaces, eof: lf (unix), codepage: UTF-8
 */
class HabraHabr extends Service {
    //TODO: Кэширование результатов парсинга

    /**
     * @see services/Service#export
     */
    public $export = FALSE;

    /**
     * @see services/Service#import
     */
    public $import = TRUE;

    /**
     * Логин хабрапользователя, чьи закладки мы будем импортировать
     * Переменная обязательна для заполнения!
     * @var string
     * @access public
     */
    public $habrauser;

    /**
     * Массив с закладками и метаданными закладок скачанных с хабры
     * @var array mixed
     * @access public
     */
    public $bookmarks = array();

    /**
     * Включать ли в импортируемый заголовок топика название блога?
     * Например: "Firefox -> Фичи в новой версии", где "Firefox" - название блога
     * По умолчанию FALSE
     * @var bool
     * @access public
     */
    public $full_header = FALSE;

    /**
     * Импортировать ли в закладки её расширенное описание?
     * По умолчанию TRUE
     * @var bool
     * @access public
     */
    public $import_content = TRUE;


    /**
     * Ограничивать описание закладки n символами
     * Опция cрабатывает когда $import_content = TRUE
     * По умолчанию 500 символов, если стоит 0, то без ограничений
     * @var int
     * @access public
     */
    public $limit_content = 500;

    /**
     * Добавлять ли к каждой импортируемой закладке предопределённый тег?
     * По умолчанию добавляется "habrahabr", если пусто, то тег добавляться не будет
     * @var string
     * @access public
     */
    public $add_import_tag = "habrahabr";

    /**
     * Импортировать ли дату в закладки.
     * Т.е. в сервисе закладок будет указана не текущая дата, а та, когда
     * вы добавляли топик на хабре в избранное (если поддерживается сервисом закладок)
     * Если FALSE, то всем закладкам будет установлена текущая дата
     * @var bool
     * @access public
     */
    public $import_date = TRUE;

    /**
     * @see services/Service#description()
     */
    public function description() {
        return "Русскоязычный сайт для гиков. Имеет огромное количество эксклюзивных статей. С него ".
        "можно импортировать ваши избранные статьи, либо избранные статьи какого-либо пользователя ".
        "ресурса <a href='http://habrahabr.ru/'>ХабраХабр</a>.";
    }

    /**
     * @see services/Service#help()
     */
    public function help() {
        return "";
    }

    /**
     * @see services/Service#exportFormRender()
     */
    public function exportFormRender() {
        return FALSE;
    }

    /**
     * @see services/Service#importFormRender()
     */
    public function importFormRender() {
        if (isset($_POST['habrauser'])) {
            //TODO: Уточнить какие требования к логинам на хабре
            if (ereg("[A-Za-z0-9_]+" , $_POST['habrauser'])) {
                $habrauser = $_POST['habrauser'];
            } else {
                throw new Exception("Не корректный логин хабрапользователя", E_USER_WARNING);
            }
        } else {
            $habrauser = "";
        }

        if (isset($_POST['full_header'])) {
            if ($_POST['full_header'] == 'on') {
                $full_header = 'checked';
            } else {
                $full_header = '';
            }
        } else {
            $full_header = '';
        }

        if (isset($_POST['import_content'])) {
             if ($_POST['import_content'] == 'on') {
                $import_content = 'checked';
             } else {
                $import_content = '';
             }
        } else {
            $import_content= 'checked';
        }

        if (isset($_POST['limit_content'])) {
             $limit_content = (int)$_POST['limit_content'];
        } else {
            $limit_content = 500;
        }

        if (isset($_POST['add_import_tag'])) {
             $add_import_tag = trim(strip_tags($_POST['add_import_tag']));
        } else {
            $add_import_tag = "habrahabr";
        }

        if (isset($_POST['import_date'])) {
             if ($_POST['import_date'] == 'on') {
                $import_date = 'checked';
             } else {
                $import_date = '';
             }
        } else {
            $import_date= 'checked';
        }

        $form = "<form name='habraForm' action='' method='post' style='text-align:left;'>
            <p>Хабралогин:
            <input type='text' value='{$habrauser}' name='habrauser' /></p>

            <p><fieldset>
                <legend><a href='javascript:showHide(\"habraDetail\")' class='detail'>Детали</a></legend>
                <div id='habraDetail' style='display:none;'>
                    <p><label><input type='checkbox' name='full_header' value='on' {$full_header}/>Включать в имортируемый заголовок название блога</label><br/>
                    <div class='help'>Например: \"Firefox → Фичи в новой версии\", где \"Firefox\" - название блога</div></p>

                    <p><label><input type='checkbox' name='import_content' value='on' {$import_content} />Импортировать в закладки её расширенное описание</label><br/>
                    <div class='help'>Примечание к закладке, кусочек текста, ясно отображающий суть статьи или страницы</div></p>

                    <p>Ограничивать описание закладки n символами <input type='text' name='limit_content' size='4' maxlength='4' value='{$limit_content}' /><br/>
                    <div class='help'>Опция cрабатывает когда птичка \"Импортировать расширенное описание\" включена. По умолчанию 500 символов, если стоит 0, то без ограничений</div></p>

                    <p>Добавлять к каждой импортируемой закладке предопределённый тег<br/><input type='text' size='50' name='add_import_tag' value='{$add_import_tag}' />
                    <div class='help'>По умолчанию добавляется \"habrahabr\", если пусто, то тег добавляться не будет.<br/>Можно добавить несколько тегов. В качестве разделителя - запятая \",\".</div></p>

                    <p><label><input type='checkbox' name='import_date' value='on' {$import_date} />Импортировать дату в закладки</label><br/>
                    <div class='help'>Если отмечено птичкой, то в сервисе закладок будет указана не текущая дата, а та, когда вы добавляли топик на хабре в избранное.<br/>
                    Если птичка не стоит, то всем закладкам будет установлена текущая дата<br/>
                    <i>(Опция актуальна, если \"дата закладки\" поддерживается сервисом закладок)</i></div></p>
                </div>
            </fieldset></p>
            <input type='hidden' name='service' value='HabraHabr' />
            <input type='hidden' name='type' value='import' />
            <p><input type='submit' value='Поехали' /></p>
        </form>
        <script type='text/javascript'>

        </script>";
        return $form;
    }

    /**
     * @see services/Service#exportBookmarks()
     */
    public function exportBookmarks($bookmarks) {
    }

    /**
     * @see services/Service#importBookmarks()
     */
    public function importBookmarks() {
        if (isset($_POST['habrauser']) && !empty($_POST['habrauser'])) {
            $this->habrauser = trim(strip_tags($_POST['habrauser']));
            //TODO: Проверить длину логина
            if (ereg("[A-Za-z0-9_]+" , $this->habrauser)) {
                if (isset($_POST['full_header']) && $_POST['full_header'] == 'on') {
                    $this->full_header = TRUE;
                } else {
                    $this->full_header = FALSE;
                }
                if (isset($_POST['import_content'])) {
                    if ($_POST['import_content'] == 'on') {
                        $this->import_content = TRUE;
                    } else {
                        $this->import_content = FALSE;
                    }
                }
                if (isset($_POST['limit_content'])) {
                    $this->limit_content = (int)$_POST['limit_content'];
                }
                if (isset($_POST['add_import_tag'])) {
                    $this->add_import_tag = trim(strip_tags($_POST['add_import_tag']));
                }
                if(isset($_POST['import_date']) && $_POST['import_date'] == 'on') {
                    $this->import_date = TRUE;
                } else {
                    $this->import_date = FALSE;
                }
                return $this->_parsePage();
            } else {
                trigger_error("Указано не корректный логин хабрапользователя.", E_USER_WARNING);
            }
        } else {
            trigger_error("Указано не корректный логин хабрапользователя.", E_USER_WARNING);
            return FALSE;
        }
    }

    /**
     * @see services/Service#exportValidate()
     */
    public function exportValidate() {
        return FALSE;
    }

    /**
     * @see services/Service#importValidate()
     */
    public function importValidate() {
      if (count($_POST) > 0) {
            //TODO: Сделать дополнительную проверку на длинну символов
            if (isset($_POST['habrauser']) && !empty($_POST['habrauser']) && ereg("[A-Za-z0-9_]+" , $_POST['habrauser'])) {
                return TRUE;
            } else {
                return FALSE;
            }
        }
        return FALSE;
    }

    /**
     * Разбираем дату хабры (которая как там указано, в формате ISO)
     * @param string $date_string
     * @access private
     * @return date | bool
     */
    private function _parseHabraDate($date_string) {
        $date = trim($date_string);
        $month = array('янв'=>'01','фев'=>'02','мар'=>'03','апр'=>'04','мая'=>'05','май'=>'05','июн'=>'06','июл'=>'07','авг'=>'08','сен'=>'09','окт'=>'10','ноя'=>'11','дек'=>'12');
        $days = explode(' ',$date);
        return strtotime("".(int)$days[2]."-".$month[mb_substr($days[1],0,3,'utf-8')]."-".sprintf("%02d",$days[0])." ".$days[3]);
    }

    /**
     * Забирает страничку. Вроде бы красивое решение на $dom->loadHTML периодически глючит, выдавая файл в неверной кодировке
     * @param $page string url - страницы, содержимое которой нужно вернуть
     * @return string | FALSE
     */
    private function _getPage($page) {
        //FIXME: Переписать эту функцию на использование CURL, чтоб можно было слать хидеры и читать их (обработка ошибок 404, 500 и т.д.)
        if (empty($page)) {
            return FALSE;
        }
        $handle = fopen($page, "r");
        $contents = '';
        while (!feof($handle)) {
            $contents .= fread($handle, 8192);
        }
        fclose($handle);
        return $contents;
    }

    /**
     * Парсит страницы хабра указанного пользователя и собирает все закладки
     * @param string Адрес следующей страницы для разбора
     * @access private
     * @return bool
     */
    private function _parsePage($next_page = '/favorites/') {

        $doc = new DOMDocument('1.0','UTF-8');
        $doc->strictErrorChecking  = TRUE;
        $doc->preserveWhiteSpace = FALSE;

        trigger_error('Разбираем страницу http://'.$this->habrauser.'.habrahabr.ru'.$next_page, E_USER_NOTICE);

        $doc->loadHTML($this->_getPage('http://'.$this->habrauser.'.habrahabr.ru'.$next_page));
        $doc->normalizeDocument();

        //TODO После получения каждой страницы парсить её, и в случае если эта страница уже есть в кэше, останавливать парсилку
        //TODO Переписать на использование XPATH

        //Выбриаем все элементы div на странице
        $divs = $doc->getElementsByTagName('div');
        foreach ( $divs as $div) {
            // Находим div с закладками
            if ($div->hasAttribute('class') && $div->getAttribute('class') == 'items') {
                // разбираем этот div на отдельные закладки
                $hentrys = $div->getElementsByTagName('div');
                foreach ($hentrys as $hentry) {
                    if ($hentry->hasAttribute('class') && mb_substr($hentry->getAttribute('class'), 0, 6) == 'hentry') {
                        // Заголовок топика (обязателен)
                        $a = $hentry->getElementsByTagName('h2')->item(0)->getElementsByTagName('a');
                        if ($this->full_header) {
                            $header = trim($a->item(0)->nodeValue).' → '.trim($a->item(1)->nodeValue);
                        } else {
                            $header = trim($a->item(1)->nodeValue);
                        }
                        // Ссылка на топик (обязательна)
                        $link = $a->item(1)->getAttribute('href');

                        // Описание топика (не обязателно)
                        $description = "";
                        $contents = $hentry->getElementsByTagName('div');
                        foreach ($contents as $content) {
                            if ($content->hasAttribute('class') && $content->getAttribute('class') == 'content' && $this->import_content == TRUE) {
                                if ($this->limit_content > 0) {
                                    if (mb_strlen($content->nodeValue) > $this->limit_content) {
                                        $description = mb_substr(trim($content->nodeValue), 0, $this->limit_content, 'utf-8') . '..';
                                    } else {
                                        $description = mb_substr(trim($content->nodeValue), 0, $this->limit_content, 'utf-8');
                                    }
                                } else {
                                    $description = trim($content->nodeValue);
                                }
                                // Получаем и обрабатываем дату публикации топика
                            } elseif ($content->hasAttribute('class') && $content->getAttribute('class') == 'published') {
                                $date = $this->_parseHabraDate($content->nodeValue);
                            }
                        }

                        // Теги (обязательны)
                        $li = $hentry->getElementsByTagName('li');
                        $tags = array();
                        foreach ($li as $tag) {
                            if ($tag->parentNode->hasAttribute('class') && $tag->parentNode->getAttribute('class') == 'tags') {
                                $tags[] = trim($tag->getElementsByTagName('a')->item(0)->nodeValue);
                            }
                        }
                        if (!empty($this->add_import_tag)) {
                            //TODO: По уму - после explode нужно у каждого элемента обрезать пробелы (Сделать эту проверку в классе Bookmarks)
                            $tags = array_merge($tags, explode(",", $this->add_import_tag));
                        }

                        // Итог. Сохраняем отпарсенную закладку в массиве. (Перенести сохранение в класс Bookmarks)
                        $this->bookmarks[] = array(
                            'header' => $header,
                            'link'   => $link,
                            'descr'  => $description,
                            'tags'   => $tags,
                            'date'   => $date
                        );
                    }
                }
            // Находим следущую страницу (если есть)
            } elseif ($div->hasAttribute('class') && $div->getAttribute('class') == 'page-nav') {
                $li = $div->getElementsByTagName('li');
                if ($li->item(1)->getElementsByTagName('a')->item(0) != null) {
                    $next_page = $li->item(1)->getElementsByTagName('a')->item(0)->getAttribute('href');
                    //Обязательная секундная задержка (по правилам хабры для роботов и пауков)
                    sleep(1);
                    $this->_parsePage($next_page);
                }
            }
        }

        //Если нет закладок - сказать, или страница не найдена
        if ( count($this->bookmarks)== 0 ){
            throw new Exception("У пользователя нет закладок на избранные статьи.", E_USER_NOTICE);
        }
        return $this->bookmarks;
    }
}
